package eMdP.classesBasiques;

import java.util.LinkedList;

import com.jme.input.InputHandler;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.input.util.SyntheticButton;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.animation.AnimationController;
import com.jme.bounding.BoundingBox;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.Spatial.CullHint;
import com.jme.scene.shape.Box;
import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.Joint;
import com.jmex.physics.JointAxis;
import com.jmex.physics.PhysicsSpace;
import com.jmex.physics.RotationalJointAxis;
import com.jmex.physics.contact.ContactInfo;
import com.jmex.physics.geometry.PhysicsCapsule;
import com.jmex.physics.geometry.PhysicsSphere;
import com.jmex.physics.material.Material;

import eMdP.gestors.GestorConstants;
import eMdP.gestors.GestorModels;


/**
 * Personatge és un Node que controla el moviment del jugador al joc. Té diversos tipus de variables,
 * per controlar els moviments, les descriptives de caire artístic, i les que contenen, el model, 
 * animacions, textures,...
 * 
 * @author Peppino Coorp.
 */

public class Personatge extends Node{
	private static final long serialVersionUID = 1L;
	
	// --------------------Variables de la classe CharacterNode
	private DynamicPhysicsNode sphere;
    private Joint joint;
    private DynamicPhysicsNode cylinder;
    private PhysicsSpace space;
    private float newspin = 0;
    private boolean allowFall = false;
    RotationalJointAxis feetWalkAxis;
	// ------------------- Fi de les Variables

	// private static final Logger logger = Logger.getLogger(eMdP.class.getName());
	// Variables que descriuen al personatge
	//Id del model del personatge
	private int idPersonatge;
	private String nom;
	private String descripcio;
	private String fotoInicial;

	// Variables que contenen la part gràfica 
	//private DynamicPhysicsNode nodeFisic;
	private Spatial model;
	private String textura;
	private String ossos;
	private AnimationController animacio;

	// Variables que defineixen les seves característiques físiques 
	private float força;
	private float velocitat;
	//private float agilitat;
	//private boolean immune;
	private float immune;

	//private int numPaquets;
	private LinkedList<Paquet> paquets;
	
	private int paquetsEntrats;
	//Variable per saber quant de temps li queda a el jugador sense poder-se moure
	private float tempsFi;
	//Id del jugador que controlarà aquest personatge
	private int idJugador;
	
	public Personatge(){
		 
	}

	
	/**
	 * Constructor que assigna els atributs del Personatge
	 * 
	 * @param nom El nom del Personatge
	 * @param descripcio Les característiques del Personatge, així com nacionalitat, i altres coses inútils
	 * @param fotoInicial Imatge que es mostrarà per escollir el Personatge
	 * @param model El model que representa gràficament el Personatge
	 * @param força Característica Física que influirà en la força que llança un Paquet el Personatge
	 * @param velocitat Característica Física que influirà en la velocitat en que es mourà el Personatge
	 */
	public Personatge(String nom, String descripcio, String textura,String model, PhysicsSpace espaiFisic, float força, int idPersonatge) {
		//Cridem al constructor del Pare
        super(nom);
        setNom(nom);
        setDescripcio(descripcio);
		// Inicialitzem la LinkedList allà a on guardarem els paquets        
        paquets = new LinkedList<Paquet>();
        // Carreguem la força que tindrà el personatge, passada per paràmetre
        setForça(força);
        // Carreguem la velocitat del PErsonatge. Serà la inversa que la força
        setVelocitat(0.9f*10);
    	// Fem que el personatge no estigui immune
    	setImmune(0);
    	// Posem el temps de bloqueix a 0 i els paquets entrats a 0
    	this.tempsFi = 0;
    	this.paquetsEntrats=0;
    	setTextura(textura);
    	setIdPersonatge(idPersonatge);
    	
        Node modelCarregat = new Node();
        setModel(GestorModels.getJMEXML(model));
        modelCarregat = (Node)this.model;
        //this.model = modelCarregat;
        Node animacions = modelCarregat;
        Node result = modelCarregat;
       // animacions = (Node)result.getChild("ArmatureSkinNode");
        //El model està a dins del fill "ArmatureSkinNode"
        result = (Node)result.getChild("ArmatureSkinNode");
        result = (Node)result.getChild(0);
        
		System.out.println("El model carregat té "+result.getTriangleCount()+" triangles.");
		System.out.println("Nom del model: "+result.getChild(0).getName());
		
		result.setModelBound(new BoundingBox());
		result.updateModelBound();
		result.updateWorldBound();
        
        //this.model = result;
        //this.model.setModelBound(new BoundingBox());
        //this.model.updateModelBound();
        //this.model.updateWorldBound();
		result.setModelBound(new BoundingBox());
		result.updateModelBound();
		result.updateWorldBound();
        this.space = espaiFisic;
        		        
        BoundingBox bbox = (BoundingBox) result.getWorldBound();
        
        if ( bbox.xExtent <= 0) {
        	bbox.xExtent = 1;}
        if ( bbox.yExtent <= 0) {
        	bbox.yExtent = 1;}
        if ( bbox.zExtent <= 0) {
        	bbox.zExtent = 1;}
        
        System.out.println("boundingCos:"+bbox);
        float cRadius = bbox.xExtent < bbox.zExtent ? bbox.zExtent : bbox.xExtent;
        float cHeight = bbox.yExtent * 2f;
        
        //Creem l'esfera que simularà els peus del personatge
        //  1. Creem el node dynamic de l'esfera
        sphere = space.createDynamicNode();
        sphere.setName("Sphere");
        //  2. Creem una esfera física que serà filla del node dinàmic esfera
        PhysicsSphere s = sphere.createSphere("BodySphere");
        sphere.attachChild(s);
        //  3. Assignem a l'esfera física el radi agafat anteriorment
        //s.setLocalScale(cRadius);
        s.setLocalScale(cRadius/2);
        //  4. Assignem el material, per tal de que és mogui de una determinada manera
        sphere.setMaterial(Material.GRANITE);
        sphere.setMass(100);
        //sphere.computeMass();
        
        //Creem la càpsula que simularà el tronc del personatge
        //  1. Creem el node dinàmic de la càpsula
        cylinder = space.createDynamicNode();
        cylinder.setName("Cylinder");
        //  2. Creem una càpsula física que serà filla del node dinàmic cylindre
        PhysicsCapsule c = cylinder.createCapsule("CapsulaCos");
        //PhysicsCylinder c = cylinder.createCylinder("BodyCylinder");
        cylinder.attachChild(c);
        //  3. Assignem el tamany d'aquesta càpusla
        //c.setLocalScale(new Vector3f(cRadius, cRadius, cHeight - cRadius));
        c.setLocalScale(new Vector3f(cRadius/2,cHeight*8,cRadius/2));
        //  4. I la posició a on estarà col·locada
        //c.setLocalTranslation(0, (cHeight - cRadius) / 2, 0);
        c.setLocalTranslation(0, cRadius, 0);
        //c.setLocalTranslation(0, cHeight - ((c.getLocalScale().y)/2f+2f*cRadius), 0);      
        System.out.println("posJugador"+c.getLocalTranslation());
        //  5. Finalment agafem la rotació cap a on ha de mirar
        Quaternion rot = new Quaternion();
        rot.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X);
        c.setLocalRotation(rot);
        cylinder.setMass(500);
        //cylinder.computeMass();
        //  6. Assignem el model al cylindre, perqué és moguin junts
        //cylinder.attachChild(this.model);
        cylinder.attachChild(result);
        //result.getLocalTranslation().addLocal(0,1.3f,0);
        result.getLocalTranslation().subtractLocal(0, cRadius/2, 0);
        this.model.getLocalTranslation().subtractLocal(0, cRadius/2, 0);
        //  7. Girem el model perquè quedi de peus
        //this.model.getLocalRotation().fromAngleAxis(FastMath.HALF_PI, new Vector3f(-1,0,0));
        result.getLocalRotation().fromAngleAxis(FastMath.HALF_PI, new Vector3f(-1,0,0));
        
        
        //Fem que tots els nodes siguin fills del Personatge
        //this.attachChild(result);
        this.sphere.setName(nom+"Esfera");
        this.attachChild(sphere);
        this.cylinder.setName(nom+"Capsula");
        this.attachChild(cylinder);
        //Creem un Joint per bloquejar la càpsula sobre l'esfera giratoria
        joint = space.createJoint();
        JointAxis axis = joint.createRotationalAxis();
        axis.setDirection(Vector3f.UNIT_X);
        joint.attach(cylinder, sphere);
        feetWalkAxis = joint.createRotationalAxis();
        feetWalkAxis.setDirection(new Vector3f(0, 0, 1));
        feetWalkAxis.setAvailableAcceleration(10);
        feetWalkAxis.setRelativeToSecondObject(true);
        //Defines if the character is or not on the ground
       /* SyntheticButton collButton = sphere.getCollisionEventHandler();
        contactDetect = new InputHandler();
        contactDetect.addAction(new InputAction() {

            @Override
            public void performAction(InputActionEvent evt) {
                ContactInfo contactInfo = (ContactInfo) evt.getTriggerData();
                Vector3f vec = contactInfo.getContactNormal(null);
                float dot = vec.dot(Vector3f.UNIT_Y);
                if (dot > -1f && dot < -0.75f) {
                }
            }
        }, collButton, false);	
        */
        cylinder.attachChild(this.model);
        // Seguidament carreguem la textura del Model i les animacions
        GestorModels.afegirTexturaXML(((Node)result), GestorConstants.DIRECCIO_TEXTURES_PERSONATGES+textura);
    	this.iniciarAnimacions((Node)animacions.getChild("ArmatureSuperBone"));
    	
    	//this.setLocalScale(2.5f);
    	//this.getLocalScale().set(2.5f, 2.5f, 2.5f);
    	
    	//Creem una caixa grossa al voltant perqué ens veigin els pnj
    	crearCaixaInvisible(bbox);
	}
	
	private void crearCaixaInvisible(BoundingBox bbox){
		Box caixa = new Box("caixaPersonatge",new Vector3f(bbox.getCenter()),bbox.xExtent,bbox.zExtent,bbox.yExtent);
		caixa.setModelBound(new BoundingBox());
		caixa.updateModelBound();
		caixa.setCullHint(CullHint.Always);
		caixa.setRenderQueueMode(Renderer.QUEUE_SKIP);
		
		this.cylinder.attachChild(caixa);
		
	}
	

    public void preventFall(float interpolation) {
        Quaternion q = cylinder.getLocalRotation();
        Vector3f[] axes = new Vector3f[3];
        q.toAxes(axes);
        q.fromAxes(axes[0], Vector3f.UNIT_Y, axes[2]);
        cylinder.setLocalRotation(q);
        cylinder.setAngularVelocity(Vector3f.ZERO.add(0, newspin, 0));
        newspin = 0;
        cylinder.updateWorldVectors();
    }

    public void update(float interpolation) {
        if (!allowFall) {
            preventFall(interpolation);
        }
        //this.netejarFisiques();
       // contactDetect.update(interpolation);    
    }

    public DynamicPhysicsNode getSphere() {
        return sphere;
    }

    public DynamicPhysicsNode getCylinder() {
        return cylinder;
    }

    public void setNewspin(float newspin) {
        this.newspin = newspin;
    }

    public void setAllowFall(boolean allowFall) {
        this.allowFall = allowFall;
    }

    public RotationalJointAxis getFeetWalkAxis() {
        return feetWalkAxis;
    }
    
    public Vector3f capOnMira(){
    	return this.cylinder.getWorldRotation().getRotationColumn(2);
    }
    
    public Vector3f posicioOnES(){
    	return sphere.getLocalTranslation();
    }
    
    @Override
    public Quaternion getLocalRotation(){
    	return cylinder.getLocalRotation();
    }
    
   /* @Override
    public Vector3f getLocalTranslation(){
    	return sphere.getLocalTranslation();
    }
    */
    
    public void netejarFisiques(){
    	this.sphere.clearDynamics();
    	this.cylinder.clearDynamics();
    }
/******************************************************************************/

	/**
	 * @return the idPersonatge
	 */
	public int getIdPersonatge() {
		return idPersonatge;
	}

	/**
	 * @param idPersonatge the idPersonatge to set
	 */
	public void setIdPersonatge(int idPersonatge) {
		this.idPersonatge = idPersonatge;
	}
	
	/**
	 * @return the paquets
	 */
	public LinkedList<Paquet> getPaquets() {
		return paquets;
	}


	/**
	 * @param paquets the paquets to set
	 */
	public void setPaquets(LinkedList<Paquet> paquets) {
		this.paquets = paquets;
	}

	/**
	 * @return the id
	 */
	public int getIdJugador() {
		return idJugador;
	}

	/**
	 * @param id the id to set
	 */
	public void setIdJugador(int id) {
		this.idJugador = id;
	}
	
	
	/**
	 * @return el nom del Personatge
	 */
	public String getNom() {
		return nom;
	}

	/**
	 * @param nom
	 *            El nom que volem posar al Personatge
	 */
	public void setNom(String nom) {
		this.nom = nom;
	}

	/**
	 * @return la descripcio del Personatge
	 */
	public String getDescripcio() {
		return descripcio;
	}

	/**
	 * @param descripcio
	 *            La descripció que volem posar al Personatge
	 */
	public void setDescripcio(String descripcio) {
		this.descripcio = descripcio;
	}

	/**
	 * @return the numPaquets
	 */
	public int getNumPaquets() {
		return paquets.size();
	}

	/**
	 * @return la fotoInicial
	 */
	public String getFotoInicial() {
		return fotoInicial;
	}

	/**
	 * @param fotoInicial
	 *            La fotoInicial que volem posar al Personatge
	 */
	public void setFotoInicial(String fotoInicial) {
		this.fotoInicial = fotoInicial;
	}

	/**
	 * @return el model
	 */
	public Spatial getModel() {
		return model;
	}

	/**
	 * @param model
	 *            el model que volem posar al Personatge
	 */
	public void setModel(Spatial model) {
	    this.model = model;
	    this.model.setModelBound(new BoundingBox());
		this.model.updateModelBound();
		this.model.updateWorldBound();
		//this.attachChild(this.model);
	}

	/**
	 * @return la textura
	 */
	public String getTextura() {
		return textura;
	}

	/**
	 * @param textura
	 *            La textura que volem posar al Personatge
	 */
	public void setTextura(String textura) {
		this.textura = textura;
	}

	/**
	 * @return els ossos
	 */
	public String getOssos() {
		return ossos;
	}

	/**
	 * @param ossos
	 *            els ossos que volem posar al Personatge
	 */
	public void setOssos(String ossos) {
		this.ossos = ossos;
	}

	/**
	 * @return la animacio
	 */
	public AnimationController getAnimacio() {
		return animacio;
	}

	/**
	 * @param animacio
	 *            l'animació que volem posar al Personatge
	 */
	public void setAnimacio(AnimationController animacio) {
		this.animacio = animacio;
	}

	/**
	 * @return la força
	 */
	public float getForça() {
		return força;
	}

	/**
	 * @param força
	 *            La força que volem posar al Personatge
	 */
	public void setForça(float força) {
		this.força = força;
	}

	/**
	 * @return la velocitat
	 */
	public float getVelocitat() {
		return velocitat;
	}

	/**
	 * @param velocitat
	 *            La velocitat que volem posar al Personatge
	 */
	public void setVelocitat(float velocitat) {
		this.velocitat = velocitat;
	}

	/**
	 * @return the immune
	 */
	public boolean isImmune(float temps) {
		return temps<this.immune;
	}

	/**
	 * @param immune the immune to set
	 */
	public void setImmune(float immune) {
		this.immune = immune;
	}
	
	
	/* ----------------------------------  Fi dels GETTES/SETTERS  -------------------------------------------- */
	
	/**
	 * Metode que retorna cert si és un PErsonatge jugador i fals altrament
	 * @return socPersonatge
	 */
	public boolean socPersonatge(){
		boolean socPersonatge = true;
		return socPersonatge;
	}
	
	/**
	 * Métode per bloquejar a un Personatge perqué no es pugui moure
	 * @param temps Temps final en que deixerà d'estar bloquejat
	 */
	public void bloquejar(float temps){
		this.tempsFi = temps;
	}
	
	
	/**
	 * Métode per saber si un Personatge es pot moure o no.
	 * @param temps Temps actual
	 */
	public boolean estaBloquejat(float temps){
		
		boolean retorn=false;
	
		if(temps>tempsFi) retorn=false;
		else retorn = true;
	
		return retorn;
	}
	
	
	public Paquet retornarEliminarPaquet(){
		Paquet p = this.paquets.removeLast();
		this.cylinder.detachChild(p);
		actualitzarPaquets();
		System.out.println("retornarEliminarPaquet: num paquets: "+this.paquets.size());
		return p;
		
	}
	
	public void actualitzarPaquets(){
		if(this.paquets.size() > 0){
			Paquet p = paquets.getLast();
			((Node)this.getChild("Fingers_R")).attachChild(p);
			p.updateRenderState();
		}
	}
	
	private Vector3f quinaEscala(){
		return this.getCylinder().getLocalScale();
	}
	
	public void carregar(PhysicsSpace espaiFisic, int numPaquets){
		
		int ultimColor = -1;
		for (int l = 0; l < numPaquets; l++)
		{
			int color = (int)Math.round((Math.random()*GestorConstants.NUM_COLORS)); //Creem un num aleatori per tal de escollir el color del paquet
			if(ultimColor != -1)
			{
				if(color == ultimColor)
					l--;
				else
				{
					Paquet paq = new Paquet("Paquet"+l+" "+color,color,espaiFisic.createDynamicNode());					
					paq.setActive(false);
					Vector3f escala = new Vector3f(1,1,1);
					paq.setLocalScale(escala.divide(this.quinaEscala()));
					paquets.addLast(paq);
					ultimColor = color;
				}
					
			}
			else
			{
				Paquet paq = new Paquet("Paquet"+color,color,espaiFisic.createDynamicNode());
				paq.setActive(false);
				Vector3f escala = new Vector3f(1,1,1);
				paq.setLocalScale(escala.divide(this.quinaEscala()));
				paquets.addLast(paq);
				ultimColor = color;
			}
		}
		actualitzarPaquets();
	}
	
	public void paquetEntrat(){
		this.paquetsEntrats+=1;
	}
	
	public int quantsPaquetsHaEntrat(){
		return this.paquetsEntrats;
	}
	
	public void mirarAlCentre(){
		this.lookAt(new Vector3f(0,0,0), new Vector3f(0,1,0));
	}
	
	private void iniciarAnimacions(Node superBone)
	{
		if(superBone.getControllerCount() > 0){
			animacio = (AnimationController)superBone.getController(0);
			System.out.println(animacio.getAnimations());
			if(animacio.hasAnimation("Esperar")){
				animacio.setActiveAnimation("Esperar");
				animacio.setActive(true);
			}
		}
	}
	
	/**
	 * Metode que et diu si l'animació actual és "Esperar"
	 * @return boolean cert si esta carregada, fals altrament
	 */
	public boolean estaEsperant()
	{
		return this.getAnimacio().getActiveAnimation().getName().equalsIgnoreCase("Esperar");
	}
	
	/**
	 * Metode que et diu si l'animació actual és "Caminar"
	 * @return boolean cert si esta carregada, fals altrament
	 */
	public boolean estaCaminant()
	{
		return this.getAnimacio().getActiveAnimation().getName().equalsIgnoreCase("Caminar");
	}
	
	/**
	 * Metode que et diu si l'animació actual és "Tirar"
	 * @return boolean cert si esta carregada, fals altrament
	 */
	public boolean estaTirant()
	{
		return this.getAnimacio().getActiveAnimation().getName().equalsIgnoreCase("Tirar");
	}
	
	public void posaCaminant()
	{
		this.getAnimacio().setActiveAnimation("Caminar");
		this.getAnimacio().setSpeed(this.getVelocitat()/10);
	}
	
	public void posaTirant()
	{
		this.getAnimacio().setActiveAnimation("Tirar");
		this.getAnimacio().setSpeed(3f);
	}
	
	public void posaEsperant()
	{
		this.getAnimacio().setActiveAnimation("Esperar");
		this.getAnimacio().setSpeed(0.3f);
	}
	
	public boolean haAcabatDeTirar()
	{
		return this.getAnimacio().getActiveAnimation().getCurrentFrame() == this.getAnimacio().getActiveAnimation().getEndFrame();
	}
}
